package com.jeasonzhao.report.engine;

import com.jeasonzhao.commons.logger.BasicLogger;
import com.jeasonzhao.commons.logger.ConsoleLogListener;
import com.jeasonzhao.commons.logger.FileLogListener;
import com.jeasonzhao.commons.logger.Logger;
import com.jeasonzhao.commons.parser.expression.EvalProviderHelper;
import com.jeasonzhao.commons.utils.ConvertEx;
import com.jeasonzhao.commons.utils.DataTypes;
import com.jeasonzhao.commons.utils.ResourceHelper;
import com.jeasonzhao.commons.xml.XMLAttribute;
import com.jeasonzhao.commons.xml.XMLHelper;
import com.jeasonzhao.commons.xml.XMLNode;
import com.jeasonzhao.report.engine.impl.XmlFileResourceLoader;
import com.jeasonzhao.report.engine.irp.IResourceLoader;
import com.jeasonzhao.report.engine.irp.IUserProvider;
import com.jeasonzhao.report.engine.repo.DatabaseConfigurationRepository;
import com.jeasonzhao.report.engine.repo.DictRepository;
import com.jeasonzhao.report.engine.repo.ExceptionRepository;
import com.jeasonzhao.report.engine.repo.ReportRepository;
import com.jeasonzhao.report.msg.MessageCodes;
import com.jeasonzhao.report.exceptions.ReportLoaderException;

public class Configuration
{
    public final static String DEFAULT_CONFIGRESNAME = "/sqlreport/com.jeasonzhao.report.engine.xml";

    private Logger m_systemLogger = null;

    private java.util.ArrayList<ReportLoaderContainer> m_configurationLoader = null;

    private com.jeasonzhao.commons.parser.expression.BasicEvalProvider m_baseEval = null;
    private IUserProvider m_userProvider = null;
    private ConfigurationTriggers m_ConfigurationTriggers = null;
    private volatile License m_License = null;
    private static Configuration m_instance = null;

    public static Configuration getInstance()
    {
        if(null == m_instance)
        {
            synchronized(Configuration.class)
            {
                m_instance = new Configuration();
            }
        }
        return m_instance;
    }

    public String getRoot()
    {
        return ResourceHelper.getClassLocationDirectory(Configuration.class);
    }
    private Configuration()
    {
        try
        {
            m_systemLogger = new BasicLogger(
                new FileLogListener("./log/","com.jeasonzhao.sqlreport")
                ,new ConsoleLogListener("SQLREPORT"));
            m_systemLogger.info(MessageCodes.format(MessageCodes.CFG_MAINLY_LOADING,DEFAULT_CONFIGRESNAME));
            XMLNode rootNode = XMLHelper.fromResource(DEFAULT_CONFIGRESNAME);
            initLogger(rootNode);
            initLoaderInformations(rootNode);
            initExpression(rootNode);
            m_ConfigurationTriggers=new ConfigurationTriggers();
            m_License=new License(rootNode.subItemOrValueOf("license"));
            m_systemLogger.info("Customize: "+this.m_License.isUsingCustomize());
            m_systemLogger.info("Cache: "+this.m_License.isUsingCache());
        }
        catch(Exception ex)
        {
            m_systemLogger.error(null,ex);
        }
    }
    public License getLicense()
    {
        return this.m_License;
    }
    public ConfigurationTriggers getTriggers()
    {
        return m_ConfigurationTriggers;
    }
    public Logger getLogger()
    {
        return m_systemLogger;
    }

    private void initLogger(XMLNode rootNode)
    {
        if(null == rootNode)
        {
            return;
        }
        m_systemLogger.info(MessageCodes.format(MessageCodes.CFG_INIT_LOGGER));
        XMLNode node = rootNode.selectNode("logger");
        if(null == node)
        {
            return;
        }
        else
        {
            String strClassName = node.valueOf("class");
            if(null == strClassName || strClassName.trim().length() < 1)
            {
                m_systemLogger.warn(MessageCodes.format(MessageCodes.CFG_NO_CLASS_LOGGER));
                return;
            }
            else
            {
                try
                {
                    Class<?> cls = Class.forName(strClassName);
                    if(com.jeasonzhao.commons.logger.Logger.class.isAssignableFrom(cls))
                    {
                        Logger agent = (com.jeasonzhao.commons.logger.Logger) cls.newInstance();
                        m_systemLogger.system(MessageCodes.format(MessageCodes.CFG_CHANGE_LOGGER,strClassName));
                        m_systemLogger = agent;
                    }
                    else if(Logger.class.isAssignableFrom(cls))
                    {
                        Logger agent = (Logger) cls.newInstance();
                        m_systemLogger.system(MessageCodes.format(MessageCodes.CFG_CHANGE_LOGGER,strClassName));
                        m_systemLogger = agent;
                    }
                    else
                    {
                        m_systemLogger.error(MessageCodes.format(MessageCodes.CFG_LOADER_ERROR,strClassName,Logger.class.getName()));
                    }
                }
                catch(Exception excep)
                {
                    m_systemLogger.error(MessageCodes.format(MessageCodes.CFG_LOADER_ERROR,strClassName,Logger.class.getName()),excep);
                }
            }
        }
    }

    private void initLoaderInformations(XMLNode rootNode)
    {
        if(null == rootNode)
        {
            return;
        }
        m_systemLogger.info(MessageCodes.format(MessageCodes.CFG_INIT_LOADER));
        for(XMLNode node : rootNode.selectNodes("loader"))
        {
            String strClassName = node.valueOf("class");
            if(null == strClassName || strClassName.trim().length() < 0)
            {
                m_systemLogger.error(MessageCodes.format(MessageCodes.CFG_LOADER_ERROR,"[NULL]",IResourceLoader.class.getName()));
                continue;
            }
            if(strClassName.trim().equalsIgnoreCase("[file]")
               || strClassName.trim().equalsIgnoreCase("[xml]")
               || strClassName.trim().equalsIgnoreCase("[filesystem]")
                )
            {
                strClassName = XmlFileResourceLoader.class.getName();
            }
            try
            {
                Class<?> cls = Class.forName(strClassName);
                if(IResourceLoader.class.isAssignableFrom(cls) == false)
                {
                    m_systemLogger.error(MessageCodes.format(MessageCodes.CFG_LOADER_ERROR,cls.getName(),IResourceLoader.class.getName()));
                }
                else
                {
                    IResourceLoader loader = (IResourceLoader) cls.newInstance();
                    for(XMLAttribute a : node.getAttributes())
                    {
                        loader.addProperty(a.getName(),a.getValue());
                    }
                    m_systemLogger.info(MessageCodes.format(MessageCodes.CFG_ADD_NEW_LOADER,cls.getName()));
                    ReportLoaderContainer lc = new ReportLoaderContainer();
                    lc.interval = ConvertEx.toInt(node.valueOf("interval"), -1);
                    lc.lastUpdate = null;
                    lc.loader = loader;
                    lc.refreshCount = 0;
                    if(null == m_configurationLoader)
                    {
                        m_configurationLoader = new java.util.ArrayList<ReportLoaderContainer>();
                    }
                    m_configurationLoader.add(lc);
                }
            }
            catch(Exception excep)
            {
                m_systemLogger.error(MessageCodes.format(MessageCodes.CFG_LOADER_ERROR,strClassName,IResourceLoader.class.getName())
                                     ,excep);
            }
        }
    }

    public void refreshConfigurations()
    {
        if(null == m_configurationLoader)
        {
            return;
        }
        synchronized(m_configurationLoader)
        {
            for(ReportLoaderContainer lc : m_configurationLoader)
            {
                boolean b = false;
                if(lc.interval > 60) //At lease one minutes
                {
                    if(lc.lastUpdate == null || lc.refreshCount < 1)
                    {
                        b = true;
                    }
                    else
                    {
                        java.util.Calendar ca = java.util.Calendar.getInstance();
                        ca.setTime(lc.lastUpdate);
                        ca.add(java.util.Calendar.SECOND,(int) lc.interval);
                        b = (new java.util.Date()).after(lc.lastUpdate);
                    }
                }
                else
                {
                    b = lc.refreshCount < 1;
                }
                if(!b)
                {
                    continue;
                }
                m_systemLogger.info(MessageCodes.format(MessageCodes.CFG_LOADER_REFRESH,lc.loader.getClass().getName()));
                try
                {
                    lc.loader.refresh(
                        this.m_systemLogger
                        ,DatabaseConfigurationRepository.getInstance()
                        ,DictRepository.getInstance()
                        ,ReportRepository.getInstance()
                        ,ExceptionRepository.getInstance());
                    lc.lastUpdate = new java.util.Date();
                    lc.refreshCount++;
                }
                catch(ReportLoaderException ex)
                {
                    m_systemLogger.warn("Refresh report by " + lc.loader.getClass().getName() + " Error",ex);
                }
            }
        }
    }

    public com.jeasonzhao.commons.parser.expression.IEvalProvider getBasicEvalProvider()
    {
        return this.m_baseEval;
    }

    private void initExpression(XMLNode rootNode)
    {
        m_baseEval = new com.jeasonzhao.commons.parser.expression.BasicEvalProvider(true);
        for(XMLNode subNode : rootNode.selectNodes("expression"))
        {
            for(XMLNode xmlFun : subNode.selectNodes("function"))
            {
                String strClassName = xmlFun.valueOf("class");
                if(null == strClassName || strClassName.trim().length() < 1)
                {
                    continue;
                }
                try
                {
                    this.m_systemLogger.info("Parse functions from class " + strClassName);
                    EvalProviderHelper.addFunctionByClass(m_baseEval
                        ,strClassName,
                        xmlFun.valueOf("prefix"),
                        xmlFun.valueOf("suffix"),
                        xmlFun.valueOf("methods")
                        );
                }
                catch(com.jeasonzhao.commons.parser.expression.EvalException ex)
                {
                    this.m_systemLogger.error("Can not parse functions from class " + strClassName,ex);
                }
            }
            for(XMLNode xmlVar : subNode.selectNodes("var"))
            {
                    String strName = xmlVar.valueOf("name");
                    if(null == strName || strName.trim().length() < 1)
                    {
                        continue;
                    }
                    String strValue = xmlVar.valueOf("value");
                    DataTypes dataType = DataTypes.from(xmlVar.valueOf("type"));
                    Object value = dataType.castValue(strValue);
                    this.m_systemLogger.info("Add Global Variable to Expression Parser [" + dataType.getName() + "]" + strName + "=" + value);
                    this.m_baseEval.addVariable(strName,value,dataType);
            }
        }

    }

    public IUserProvider getUserProvider()
    {
        return this.m_userProvider;
    }
    public boolean isAnonymousAccess()
    {
        return true;
    }
}
